package com.yuneec.droneservice.request;

import android.os.SystemClock;
import android.util.SparseArray;

import com.yuneec.droneservice.ServiceManager;
import com.yuneec.droneservice.connection.ConnectionInterface;
import com.yuneec.droneservice.connection.DataReceivedListener;
import com.yuneec.droneservice.protocol.ProtocolPlugin;
import com.yuneec.droneservice.tools.RCLog;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by zd.zhang on 2017/9/11.
 */

public class DispatchService implements DataReceivedListener {
    private SparseArray<LinkedList<OnRequestListener>> mListeners;
    private SparseArray<IRequest> mRequests;
    private ArrayList<Integer> removeCacheList;
    private LinkedList<OnRequestListener> notTimeOutCacheList;
    private ReentrantLock dispathcLock = new ReentrantLock();
    private boolean isStart = false;
    private boolean isRunning = false;
    private ExecutorService handlerThreader;
    private ConnectionInterface connection;
    private ProtocolPlugin plugin;

    public DispatchService(ConnectionInterface connection, ProtocolPlugin plugin) {
        mListeners = new SparseArray<>(30);
        mRequests = new SparseArray<>(30);
        removeCacheList = new ArrayList<>(30);
        notTimeOutCacheList = new LinkedList<>();
        this.connection = connection;
        this.plugin = plugin;
    }

    public void start() {
        if (!isStart && !isRunning) {
            dispathcLock.lock();
            try {
                //synchronized (lock){
                if (!isStart && !isRunning) {
                    handlerThreader = Executors.newCachedThreadPool();
                    isStart = true;
                    handlerThreader.execute(new Runnable() {
                        @Override
                        public void run() {
                            isRunning = true;
                            while (isStart) {
                                SystemClock.sleep(1000);
                                checkTimeOut();
                            }
                            isRunning = false;
                        }
                    });
                }
                //}
            } finally {
                dispathcLock.unlock();
            }
        }
    }

    public void stop() {
        if (isStart) {
            dispathcLock.lock();
            try {
                // synchronized (lock) {
                if (isStart) {
                    isStart = false;
                }
                //}
            } finally {
                dispathcLock.unlock();
            }
        }
    }

    private final void checkTimeOut() {
        dispathcLock.lock();
        try {
            //synchronized (lock) {
            removeCacheList.clear();
            notTimeOutCacheList.clear();
            long currentTime = System.currentTimeMillis();
            for (int i = 0; i < mListeners.size(); i++) {
                int ids = mListeners.keyAt(i);
                LinkedList<OnRequestListener> listeners = mListeners.valueAt(i);
                if (listeners == null) {
                    removeCacheList.add(ids);
                    continue;
                }

                while (listeners.size() > 0) {
                    OnRequestListener listener = listeners.removeFirst();
                    if (currentTime - listener.sendTimeStamp > listener.getTimeout()) {
                        handlerThreader.execute(new TimeOutHandler(ids, listener));
                    } else {
                        notTimeOutCacheList.addLast(listener);
                    }
                }
                if (notTimeOutCacheList.size() == 0) {
                    removeCacheList.add(ids);
                } else {
                    for (OnRequestListener notOutTime : notTimeOutCacheList) {
                        listeners.addLast(notOutTime);
                    }
                    notTimeOutCacheList.clear();
                }
            }
            for (int ids : removeCacheList) {
                mRequests.remove(ids);
            }
            //}
        } finally {
            dispathcLock.unlock();
        }
    }

    private static final class OnErrorHandler implements Runnable {
        private int ids;
        private int errorCode;
        private OnRequestListener listener;

        public OnErrorHandler(int ids, int errorCode, OnRequestListener listener) {
            super();
            this.ids = ids;
            this.errorCode = errorCode;
            this.listener = listener;
        }

        @Override
        public void run() {
            RCLog.d(String.format("OnErrorHandler(id:%d):%d", ids, errorCode));
            try {
                this.listener.onError(errorCode);
            } catch (Exception e) {
                RCLog.e(String.format("OnErrorHandler(id: %d ) error:%s", ids, e.getMessage()));
            }
        }
    }

    private static final class TimeOutHandler implements Runnable {
        private int ids;
        private OnRequestListener listener;

        private TimeOutHandler(int ids, OnRequestListener listener) {
            super();
            this.ids = ids;
            this.listener = listener;
        }

        @Override
        public void run() {
            try {
                this.listener.onTimeout();
            } catch (Exception e) {
                RCLog.e(String.format("TimeOutHandler(id: %d ) error:%s", ids, e.getMessage()));
            }
        }
    }

    public void onReceive(byte[] buf, int off, int len) {
        try {
            if (buf == null || len <= 0 || off < 0 || buf.length < off + len) {
                return;
            }
            ProtocolPlugin plugin = ServiceManager.getInstance().getProtocolPlugin();
            if (plugin == null) {
                return;
            }
            IResponseHandler irh = plugin.handleResponseBuf(buf, off, len);
            if (irh == null) {
                return;
            }
            int ids = irh.ids();
            if (ids != -1) {
                OnRequestListener listener = null;
                IRequest request = null;
                dispathcLock.lock();
                try {
                    //synchronized (lock) {
                    request = mRequests.get(ids);
                    LinkedList<OnRequestListener> listeners = mListeners.get(ids);
                    if (listeners != null && listeners.size() > 0) {
                        listener = listeners.removeFirst();
                    }
                    if (listeners == null || listeners.size() == 0) {
                        mRequests.remove(ids);
                    }
                    //}
                } catch (Exception e) {
                    RCLog.d("dispatchService.onReceive", e);
                } finally {
                    dispathcLock.unlock();
                }
                if (listener == null || request == null) {
                    return;
                }
                if (request.parseReply(irh, listener)) {
                    handlerThreader.execute(irh);
                }
            } else {
                handlerThreader.execute(irh);
            }
        } catch (Exception e) {
            RCLog.e(String.format("receive data buf 2 response error:%s", e.getMessage()));
        }


    }

    public void sendCommand(IRequest request, OnRequestListener<?> listener) {
        if (plugin == null) {
            return;
        }
        int id = request.ids();
        byte[] buf = plugin.toRawData(request);
        if (request.hasReply() && listener != null) {
            listener.sendTimeStamp = System.currentTimeMillis();
            Runnable handler = null;
            dispathcLock.lock();
            try {
                //synchronized (lock) {
                LinkedList<OnRequestListener> thisListener = mListeners.get(id);
                IRequest thisReq = mRequests.get(id);
                if (thisReq == null) {
                    mRequests.put(id, request);
                }
                if (thisListener == null) {
                    thisListener = new LinkedList<>();
                    mListeners.put(id, thisListener);
                }
                thisListener.addLast(listener);
                //}
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                dispathcLock.unlock();
            }
            if (handler != null) {
                handlerThreader.execute(handler);
            }
        }
        connection.writeData(request.port(), buf);
    }

}
